package taller2.view.draws;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.util.ArrayList;

import javax.swing.ImageIcon;

import taller2.interfaces.Connection;
import taller2.interfaces.Drawable;
import taller2.interfaces.Graph;
import taller2.interfaces.Node;
import taller2.utils.Constants;
import taller2.utils.Constants.Colores;
import taller2.utils.Point;

public class GraphDrawer implements Drawable{
	private Graph graph;
	private Node  selectedNode;
	private Connection  selectedConnection,selectedConnectionNotDirected;	
	
	// private Image nodeMarkedImg;
	private Color unselectedConnectionColor,selectedConnectionColor;
	
	private int imgSize;
	private float connSize;
	
	private float fontSize;
	private Point ConnectionOff;
	private double distMax;
	
	// constructor
	public GraphDrawer(Graph graph,Color selectedColor, Color unSelectedColor){
		this.graph = graph;
		this.selectedNode = null;
		this.selectedConnection = null;
		this.selectedConnectionNotDirected = null;
		
		this.selectedConnectionColor = selectedColor;
		this.unselectedConnectionColor = unSelectedColor;
		resize(50, 4, 1);
	}
	
	// draws all nodes and connections
	public void draw(Graphics g){
		graph.unmarkNodes();
		
		for(Node node : graph.getNodes()){
			drawConnections(g,node,graph.isDirected());			
		}
		
		for(Node node : graph.getNodes()){
			Image img = new ImageIcon(Constants.yellowNodePath).getImage();;

			if(node.getColor() == Colores.yellow){
				img = new ImageIcon(Constants.yellowNodePath).getImage();
			}else if(node.getColor() == Colores.red){
				img = new ImageIcon(Constants.redNodePath).getImage();
			}else if(node.getColor() == Colores.purple){
				img = new ImageIcon(Constants.purpleNodePath).getImage();
			}else if(node.getColor() == Colores.green){
				img = new ImageIcon(Constants.greenNodePath).getImage();
			}else if(node.getColor() == Colores.greenRed){
				img = new ImageIcon(Constants.redGreenNodePath).getImage();
			}else if(node.getColor() == Colores.greenPurple){
				img = new ImageIcon(Constants.purleGreenNodePath).getImage();
			}else if(node.getColor() == Colores.redPurple){
				img = new ImageIcon(Constants.redPurpleNodePath).getImage();
			}
			
			int posX = node.getPos().getX()-imgSize/2+1;
			int posY = node.getPos().getY()-imgSize/2+1;
			
			g.drawImage(img,posX,posY,imgSize,imgSize,null);
			
			String tag = node.getTag();
			drawTag(g,tag,posX,posY);
			
			String marker = node.getMarker();
			drawDijstraMarker(g,marker,posX+imgSize/2,posY);
		}
		graph.unmarkNodes();
	}
	
	// draws the marker for the dijstra's algorithm
	private void drawDijstraMarker(Graphics g,String tag,int posX,int posY){
		drawText(g,tag,posX,posY,Constants.dijstraMarkerColor);
	}
	
	// draws the tag for the node
	private void drawTag(Graphics g,String tag,int posX,int posY){
		drawText(g,tag,posX,posY,Constants.tagFontColor);
	}
	
	// draws a piece of text
	private void drawText(Graphics g,String text,int posX,int posY, Color color){
		if (text != null){
			g.setColor(color);
			g.setFont(new Font("Serif",Font.PLAIN,(int)(3*fontSize+connSize)));
			g.drawString(text,posX,posY);
		}		
	}
	
	// draws lines for all the connections for the node
	private void drawConnections(Graphics g, Node node,boolean directed) {
		for (Connection connection : node.getConnections()){
			
			Graphics2D g2 = (Graphics2D)g;
			g2.setStroke(new BasicStroke(connSize));
	
			boolean isSelected = connection.isSelected();
			Color color = (isSelected)?selectedConnectionColor:unselectedConnectionColor;
			g2.setColor(color);

			Node otherNode = connection.getOtherNode();
			Point pos = node.getPos().add(ConnectionOff);
			Point posOther = otherNode.getPos().add(ConnectionOff);
			
			String weight = String.valueOf(connection.getWeight());

			String capacity = String.valueOf(connection.getCapacity());
			String flux = String.valueOf(connection.getFlux());
			
			if(node == otherNode){
				drawCircle(g2,pos,weight);
			}else{
				if(directed){
					drawCurve(g2,pos,posOther,weight,flux+"/"+capacity,Constants.curvyness,false);
				}else{
					if(!otherNode.isMarked())
						weight = ""; 
					
					drawCurve(g2,pos,posOther,weight,flux+"/"+capacity,0,true);
				}
			}
		}
		node.mark();
	}
	
	// draws a circle to show the node is connected to itself
	private void drawCircle(Graphics2D g, Point pos, String wght){
		int radius = (int) (connSize*8);
		double delta = imgSize*0.1;

		int centerX = (int) (pos.getX()-delta-(radius/2));
		int centerY = (int) (pos.getY()-delta-(radius/2));		
		
	
		g.drawOval(centerX,centerY,radius,radius);
		
		if(!wght.matches(String.valueOf(Constants.ConnectionKilled))){
			g.setFont(new Font("Serif",Font.PLAIN,(int)(3*fontSize+connSize)));
			g.setColor(Constants.fontColor);
			g.drawString(wght, (int)(centerX+3*delta), (int)(centerY-2*delta));
		}
	}
	
	// draws the connection as a curve between the two nodes
	private void drawCurve(Graphics2D g, Point pos1, Point pos2, String wght, String flux, float curviness, boolean dontDrawArrow){
		
		int posX1 = pos1.getX()-imgSize/2+1;
		int posY1 = pos1.getY()-imgSize/2+1;
		int posX2 = pos2.getX()-imgSize/2+1;
		int posY2 = pos2.getY()-imgSize/2+1;
		
		double dirX  = (posX1-posX2);
		double dirY  = (posY1-posY2);
		double passX = posX2;
		double passY = posY2;
		double dist = (imgSize/3)/(Math.sqrt(dirX*dirX+dirY*dirY));
		
		int posXf = (int) (dist*dirX+passX);
		int posYf = (int) (dist*dirY+passY);
		
		String weight = new String(wght);
		CurvedArrow curve = new CurvedArrow(posX1,posY1,posXf,posYf,curviness,weight,flux);

		if(dontDrawArrow)
			curve.dontDrawArrow();
		
		g.setFont(new Font("Serif",Font.PLAIN,(int)(3*fontSize+connSize)));
		curve.draw(g);
	}
	
	// returns true if no node is selected
	public boolean noNodesSelected(){
		return (selectedNode == null);
	}
	
	// selects the connection that goes from node1 to node2 from their index
	public void selectConnection(int nodeIndex1,int nodeIndex2){
		ArrayList<Node> nodes = graph.getNodes();
		Node node1 = nodes.get(nodeIndex1);
		Node node2 = nodes.get(nodeIndex2);
		
		selectConnection(node1,node2);
	}

	// selects the connection that goes from node1 to node2
	public void selectConnection(Node node1,Node node2){
		for(Connection connection:node1.getConnections()){
			if(connection.getOtherNode() == node2){
				if(selectedConnection != null)
					selectedConnection.unselect();
				selectedConnection = connection;
				connection.select();
				
				if(!graph.isDirected()){	// to select both connections in a non directed graph...	
					for(Connection connectionNotDirected:node2.getConnections()){
						if(connectionNotDirected.getOtherNode() == node1){
							if(selectedConnectionNotDirected != null)
								selectedConnectionNotDirected.unselect();
							selectedConnectionNotDirected = connectionNotDirected;
							connectionNotDirected.select();
						}						
					}					
				}
				
				break;
			}
		}		
	}

	public Node getCloseByNode(Point pos) {
		ArrayList<Node> nodes = graph.getNodes();
		for(Node node : nodes){
			if(isCloseEnough(node,pos)){
				return node;
			}
		}
		return null;
	}

	// selects the node that is close to the point
	// returns false if no node is close enough
	public boolean selectCloseByNode(Point posObj){
		ArrayList<Node> nodes = graph.getNodes();
		for(Node node : nodes){
			if(isCloseEnough(node,posObj)){
				selectedNode = node;
				selectedNode.select();
				selectedNode.changeColor(Colores.red);
				return true;
			}
		}
		
		unselectNode();
		return false;
	}	
	
	// to be called after loading a new graph to update the selected node and connection
	public void loadSelection(){
		ArrayList<Node> nodes = graph.getNodes();
		for(Node node : nodes){
			if(node.isSelected()){
				selectedNode = node;
				node.select();
				break;
			}
		}
		
		for(Node node : nodes){
			for(Connection connection:node.getConnections()){
				if(connection.isSelected()){
					selectConnection(node,connection.getOtherNode());
					break;
				}
			}
		}
	}
	
	// returns the index of the selected node or -1
	// if no node is selected
	public int getSelectedNodeIndex(){
		if (selectedNode != null)
			return graph.getNodeIndex(selectedNode);
		return -1;
	}
	
	// unselects the selected node
	public void unselectNode(){
		if(selectedNode != null){
			selectedNode.unselect();
			selectedNode.changeColor(Colores.yellow);
			selectedNode = null;	
		}
	}
	
	// returns whether if the Point is close enough to grab the node
	public boolean isCloseEnough(Node node, Point posObj){
		Point pos = node.getPos();
		Point sub = posObj.substract(pos);
		double dist = sub.norm();
		return (dist<distMax);
	}

	// cnanges the marker for the selected node
	public void changeMarkerForSelectedNode(String marker) {
		if (marker != null)
			selectedNode.setMarker(marker);
	}	
	
	// cnanges the tag for the selected node
	public void changeTagForSelectedNode(String tag){
		if (tag != null)
			selectedNode.setTag(tag);
	}
	
	// moves the selected node with the mouse
	public void moveSelectedNodeWithMouse(Point initMouse) {
		selectedNode.setPos(initMouse);
	}	
	
	// resizes the nodes and connections
	public void resize(int newImageSize, float newConnectionSize,float newFontSize){
		imgSize = newImageSize;
		connSize = newConnectionSize;
		
		ConnectionOff= new Point(imgSize/2,imgSize/2);
		distMax = imgSize/2;
		
		fontSize = Constants.fontSize*newFontSize;
	}
	
	public void setGraph(Graph graph) {
		this.graph = graph;
	}

}
